import java.util.ArrayList;
import java.util.List;
import java.util.Map;
//import WavFile.*;
import SFT.*;

/*
 * This basically static class is responsible for the integration of the Central-SFT-Project with our code, thus
 * handling the bridging necessary between the two projects.  
 */
public class Integration {
	
	/*
	 * given a WAV file and a period, runs the SFT algorithm on it to get a type 
	 * OutputClass of data that is a list of the heavy frequencies of the WAV file.
	 */
	
	final static double silenceError = 3.7;
	
	/*
	 * given a Wav file name, and a period, uses the central algorithm to decompose theWav file and thus
	 * learn it through it's heavy frequencies.
	 */
	public static WavFileInfo GetFrequencies(String wavFilename, double period){ 
		WavFileInfo output = new WavFileInfo();
		output = WavHandler.learnWavFile(wavFilename, "PEAKS", "DET", "FIRST", period, 10);
		return output;
	}
	
	
	/*
	 * given a list of heavy frequencies as a WavFileInfo object, returns a MusicItem of 
	 * the Notes with the highest amplitude, i.e. the hashing function of the project.
	 */
	public static MusicItem GetMusicItemFromFrequencies(WavFileInfo sftOutput){
		MusicItem music = new MusicItem();
		List<Double> freqList = GetFreqListFromWavFileInfo(sftOutput);
		for(double freq : freqList){
			Note note = new Note(freq);
			music.getCloister().add(note);
		}
		return music;
	}
	
	/*
	 * given a map of heavy coefficients, returns the one with the highest Amplitude. 
	 */
	private static double getMaxAmp(Map<Integer, Complex> myMap, double wave) {
		double maxValue = 0;
		double maxFreq = 1;
		double keyDoub;
		double period = 44100.0 / wave;
		double halfPeriod = period * 0.5;
		if (myMap != null) {
			for (Map.Entry<Integer, Complex> entry : myMap.entrySet()) {
				Integer key = (Integer) entry.getKey();
				keyDoub = (double) key;
				Complex value = (Complex) entry.getValue();
				double absValue = norm(value);
				if (keyDoub <= halfPeriod) {
					if (absValue > maxValue) {
						maxValue = absValue;
						maxFreq = keyDoub;
					}
					else if (absValue == maxValue && keyDoub < maxFreq) {
						maxFreq = keyDoub;
					}
				}
			}
		}
		return maxFreq;
	}

	
	
	private static double norm(Complex value) {
		return (value.Re()*value.Re() + value.Im()*value.Im());
	}
	
	/*
	 * given an Output class of the SFT output of heavy coefficients, returns a list
	 *  of only the frequencies with the highest amplitude.
	 */
	public static List<Double> GetFreqListFromWavFileInfo(WavFileInfo sftOutput)
	{
		List<Double> freqList = new ArrayList<Double>();
		ChannelInfo firstChannel = sftOutput.getChannelist().get(0);
		double freq = 0;
		double coeff, wave;
		Map<Integer,Complex> coeffsAmp;
		for(PeriodicityInfo p : firstChannel.getPeriods()){
			wave = p.getFrequency();
			coeffsAmp = p.getHeavyCharacters();
			coeff = getMaxAmp(coeffsAmp, wave);
			freq = coeff*wave;
			freqList.add(freq);
		}
		
		return freqList;
	}
	
	
	/*
	 * the main algorithm. given a MusicItem, a Library and a starting num the 
	 * algorithm tries to find the MusicFile that best fits the given MusicItem 
	 * and suggests it.
	 */
	public static MusicFile Algo(MusicItem input, Library lib, int start){ 
		double minNorm = Double.MAX_VALUE;
		MusicFile curr_music_file = null;
		MusicFile return_music_file = null;
		MusicItem curr_music_item = null;
		int[] mat = new int[8]; //the score for every song
		Note noteUser = null;
		Note noteSong = null;
		int curr_music_file_num = start;
		int dist = 0;
		int offset = 0;
		int curr_note = 0;
		int inputLength = input.getCloister().size();
		int nonZeroLength = input.getCloister().size();
		LibraryInformation info = lib.getLibraryInformation();
		int database_size = info.getNum();
		while (curr_music_file_num < database_size){ //while there are songs in database
			curr_music_file = Library.LoadMusicFile(curr_music_file_num);
			curr_music_item = curr_music_file.getMusic();
			curr_note = 0;
			offset = 0;
			
			while(offset + inputLength < curr_music_item.getCloister().size()){//covers the offsets, also checks that the input isn't longer than the curr_song
				nonZeroLength = inputLength;
				noteUser = input.getCloister().get(curr_note);
				noteSong = curr_music_item.getCloister().get(offset);
				curr_note = 0;
				while(curr_note < input.getCloister().size()-1) { //while there are still notes in songs and in user input
					if (noteUser.getMusicalNote() == 0 || noteSong.getMusicalNote() == 0) {
						if (noteUser.getMusicalNote() == 0 && noteSong.getMusicalNote() == 0)
							dist = -2;
						else
							dist = -1;
					}
					else {
						dist = getDistance(noteUser.getMusicalNote(), noteSong.getMusicalNote());
					}
					updateMat(mat, dist);
					curr_note++;
					noteUser = input.getCloister().get(curr_note);
					noteSong = curr_music_item.getCloister().get(offset + curr_note);
				}
				if(nonZeroLength < 0.3 * inputLength)//bad sample, too many unhandled notes or blanks in the sample or song
				{
					clearMat(mat);
					offset++;
					continue;
				}
				
				if(minNorm > calcMatNorm(mat, nonZeroLength)){//checks that the current offset has better Norm than previous tries
					minNorm = calcMatNorm(mat, nonZeroLength);
					return_music_file = curr_music_file;
				}
				clearMat(mat);
				offset++;
			}
			clearMat(mat);
			curr_music_file_num++;
			if(curr_music_file_num != database_size)
				curr_music_file = Library.LoadMusicFile(curr_music_file_num);
		}
		return return_music_file;
	}
	
	
	/*
	 * returns the minimal value of the given Matrix divided by the given length 
	 */
	private static double calcMatNorm(int[] mat, int length){
		double min = Double.MAX_VALUE;
		double a;
		double minA = Double.MAX_VALUE;
		for(int i = 0; i < 7; i++){
			a = (double) mat[i];
			if(minA > a)
				minA = a;
		}
		
		min = mat[7]*silenceError;
		min += minA;
		return min;
	}

	/*
	 * update the song score according to new distance between song's note and humming's note
	 */
	private static void updateMat(int[] mat, int dist) {
		
		if (dist == -1) {
			mat[7]++;
		}
		else if (dist != -2) {
			int num = dist;
			int ret;
			for (int i=2; i>=0; i--) {
				num++;
				if (num > 6)
					num -= 12;
				ret = absInt(num);
				mat[i] += ret;
			}
			num = dist;
			for (int i=4; i<7; i++) {
				num--;
				if (num > -6)
					num += 12;
				ret = absInt(num);
				mat[i] += ret;
			}
			mat[3] += absInt(dist);
		}
	}
	
	private static int absInt(int num) {
		int ret = num;
		if (ret < 0)
			ret = -1*ret;
		return ret;
	}

	/*
	 * get the distance between the song's note and the humming's note
	 */
	private static int getDistance(int a, int b) {
		
		int num = b-a;
		if (num > 6)
			num -= 12;
		if (num < -6)
			num += 12;
		return num;
	}
	
	private static void clearMat(int[] mat) {
		for (int i=0;i<mat.length; i++)
			mat[i] = 0;
	}

	
}
